import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize
import warnings
warnings.filterwarnings('ignore')
from sklearn.preprocessing import MinMaxScaler
df = pd.read_csv('./nasdaq_d_2012_2022.csv')
df = df.pivot(index="date",columns="tic", values="adjcp")
df.index = pd.to_datetime(df.index, format = '%Y-%m-%d')
tick = ['TSLA', 'NVDA', 'AMD', 'AVGO', 'ALGN', 'LRCX', 'FTNT',
'ODFL', 'AMAT','MSFT', 'MU', 'IDXX', 'KLAC', 'CDNS',
'MRVL', 'ASML', 'FB', 'CTAS','INTU', 'SNPS']
df = df.loc[:,tick]
#TODO:NOISE 随机扰动
class portOptim(object):
def __init__(self,real_df,pred_df, dynamic_win = True):
self.df = real_df.iloc[1:,:]
self.date = pred_df.index
self.pct_dt = real_df.pct_change().iloc[1:,:]
self.pred_df = pred_df
self.day = 0
self.max_day = pred_df.shape[0]
self.covWin = 30
self.data = self.df.iloc[self.day:self.day+self.covWin,:]
self.init_action = self.action = np.array([0]*20)
self.mu = 0
self.cov = 0
self.risk_re = 0.5
self.dynamic_win = dynamic_win
#portfolio
self.p = lambda A: self.asset + np.append(-A.sum()-abs(A).sum()*self.tran_cost,A)
#weight
self.w = lambda A: (self.p(A) / np.sum(self.p(A)))
#Penalties for handling fees
self.pena = lambda A: abs(A).sum()/self.p(A).sum()
#sharpe ratio
self.SR = lambda A: (self.mu@self.w(A)-self.pena(A))/np.sqrt(self.w(A)@self.cov@self.w(A))
self.op = lambda A: -self.SR(A)
ones = np.ones(21)
self.cons = ({'type': 'ineq', 'fun': lambda A: self.w(A)},
{'type': 'eq', 'fun': lambda A: self.w(A) @ ones - 1})
self.tran_cost = 0.001
self.rf = 0.06/365
self.asset = self.weight = np.array([1/21]*21)
self.scaler = MinMaxScaler(feature_range=(-30,-5))
self.action_memory = []
self.asset_memory = []
self.tick = np.array(tick)
print(f'Day{self.day}, initial portfolio {self.asset} , total {round(self.asset.sum(),2)}')
def get_mu(self):
cov = np.array(self.data.cov())
var = cov.diagonal().reshape(20,1)
win_s = self.scaler.fit_transform(var).astype('int').flatten()
pred_c = np.array(self.pred_df.iloc[self.day,:]/self.df.iloc[self.day+self.covWin-1,:])
mu = []
for i in range(len(win_s)):
pct_c = self.data.iloc[win_s[i]:,i]
mu.append(pct_c.mean())
dynamic_mu = np.append(self.rf,np.array(mu*pred_c-1)/(win_s+1))
normal_mu = np.append(self.rf,np.array(self.data.mean()+pred_c/self.covWin))
print(f'Var\n{var.squeeze()}\n winSize\n {win_s}\n mu \n{dynamic_mu}')
return dynamic_mu if self.dynamic_win else normal_mu
def optim(self):
result = minimize(self.op, x0=(self.action), method='trust-constr', constraints=self.cons)
return result.x
def step(self):
if self.day>=self.max_day:
print('Finished.')
return True
self.data = self.pct_dt.iloc[self.day:self.day+self.covWin,:]
self.mu = self.get_mu()
cov = self.data.cov()
self.cov = np.block([[0,np.zeros(20)],[np.zeros([20,1]),cov]])
#get the action for achive the SR maximum
self.action = self.init_action
self.action = self.optim().astype('float64')
SR = self.SR(self.action)
self.action_memory.append(self.action)
print(f'Action:\n {self.action}')
real_c = np.array(self.df.iloc[self.day+self.covWin,:]/self.df.iloc[self.day+self.covWin-1,:])
print(f'Real return {real_c}')
self.asset = self.p(self.action)*np.append(1+self.rf,real_c)
self.asset_memory.append(self.asset)
print(f'Asset:\n {self.asset}')
#calculate the real asset
self.weight = self.asset/self.asset.sum()
self.day+=1
top_3_idx = self.weight[1:].argsort()[-3:][::-1]
top_3 = self.tick[top_3_idx]
print(f'Day{self.day}, {self.date[self.day-1]}, SR {SR}, risk free {round(self.weight[0],2)} TOP3 {top_3},################ total {round(self.asset.sum(),2)}')
print('#######################################################')
return False
def perf(self):
while(1):
done = self.step()
if done:
break
lag = 0
df_pred = pd.read_csv("./prediction.csv", index_col=0).iloc[lag:,:]
df_real = df.loc['2019-11-15':,:].iloc[lag:,:]
model = portOptim(df_real,df_pred,dynamic_win = True)
Day0, initial portfolio [0.04761905 0.04761905 0.04761905 0.04761905 0.04761905 0.04761905 0.04761905 0.04761905 0.04761905 0.04761905 0.04761905 0.04761905 0.04761905 0.04761905 0.04761905 0.04761905 0.04761905 0.04761905 0.04761905 0.04761905 0.04761905] , total 1.0
model.perf()
dynamic = pd.DataFrame(index = df_pred.index)
dynamic['value'] = np.array(model.asset_memory).sum(axis=1)-1
# cumReturn.to_csv('./dynamic_win.csv')
dynamic = pd.read_csv('./dynamic_win.csv').set_index('date')
# dynamic
NDX = pd.read_csv('./NDX_d_2012_2022.csv').set_index('date').loc['2019-12-31':,]
NDX_return = (NDX.adjcp.pct_change()+1).cumprod().dropna()-1
fixed = pd.read_csv('./fixed_window.csv')
from datetime import datetime as dt
import pyfolio
import matplotlib.pyplot as plt
import plotly
import plotly.graph_objs as go
time_ind = pd.Series(dynamic.index)
trace_dynamic = go.Scatter(x = time_ind, y = dynamic.value, mode = 'lines', name = 'max-SR(with dynamic window)')
trace_fixed = go.Scatter(x = time_ind, y = fixed.value, mode = 'lines', name = 'max-SR(with fixed windows)')
trace_baseline = go.Scatter(x = time_ind, y = NDX_return, mode = 'lines', name = 'NDX')
fig = go.Figure()
fig.add_trace(trace_dynamic)
fig.add_trace(trace_fixed)
fig.add_trace(trace_baseline)
fig.update_layout(
legend=dict(
x=0,
y=1,
traceorder="normal",
font=dict(
family="sans-serif",
size=15,
color="black"
),
bgcolor="White",
bordercolor="white",
borderwidth=2
),
)
#fig.update_layout(legend_orientation="h")
fig.update_layout(title={
#'text': "Cumulative Return using FinRL",
'y':0.85,
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'})
#with Transaction cost
#fig.update_layout(title = 'Quarterly Trade Date')
fig.update_layout(
# margin=dict(l=20, r=20, t=20, b=20),
paper_bgcolor='rgba(1,1,0,0)',
plot_bgcolor='rgba(1, 1, 0, 0)',
#xaxis_title="Date",
yaxis_title="Cumulative Return",
xaxis={'type': 'date',
'tick0': time_ind[0],
'tickmode': 'linear',
'dtick': 86400000.0 *80}
)
fig.update_xaxes(showline=True,linecolor='black',showgrid=True, gridwidth=1, gridcolor='LightSteelBlue',mirror=True)
fig.update_yaxes(showline=True,linecolor='black',showgrid=True, gridwidth=1, gridcolor='LightSteelBlue',mirror=True)
fig.update_yaxes(zeroline=True, zerolinewidth=1, zerolinecolor='LightSteelBlue')
fig.show()
baseline = np.append(1,np.array(NDX_return)+1)
baseline = (baseline[1:]/baseline[:-1]-1)
baseline = pd.DataFrame(baseline, index = pd.to_datetime(NDX_return.index),columns = ['return'])
dynamic_r = np.append(1,np.array(dynamic.value)+1)
dynamic_r = (dynamic_r[1:]/dynamic_r[:-1]-1).astype('float64')
dynamic_r = pd.DataFrame(dynamic_r,columns = ['return'])
dynamic_r.set_index(pd.to_datetime(dynamic.index),inplace = True)
fixed_r = np.append(1,np.array(fixed.value)+1)
fixed_r = (fixed_r[1:]/fixed_r[:-1]-1).astype('float64')
fixed_r = pd.DataFrame(fixed_r,columns = ['return'])
fixed_r.set_index(pd.to_datetime(dynamic.index),inplace = True)
with pyfolio.plotting.plotting_context(font_scale=1.1):
pyfolio.create_full_tear_sheet(returns = dynamic_r['return'],
benchmark_rets=baseline['return'], set_context=False)
| Start date | 2020-01-02 | |
|---|---|---|
| End date | 2021-12-31 | |
| Total months | 24 | |
| Backtest | ||
| Annual return | 64.454% | |
| Cumulative returns | 170.987% | |
| Annual volatility | 55.18% | |
| Sharpe ratio | 1.18 | |
| Calmar ratio | 1.17 | |
| Stability | 0.67 | |
| Max drawdown | -55.226% | |
| Omega ratio | 1.24 | |
| Sortino ratio | 1.78 | |
| Skew | 0.14 | |
| Kurtosis | 4.39 | |
| Tail ratio | 1.03 | |
| Daily value at risk | -6.694% | |
| Alpha | 0.20 | |
| Beta | 1.32 | |
| Worst drawdown periods | Net drawdown in % | Peak date | Valley date | Recovery date | Duration |
|---|---|---|---|---|---|
| 0 | 55.23 | 2020-02-19 | 2020-03-18 | 2020-08-12 | 126 |
| 1 | 30.70 | 2021-01-25 | 2021-05-19 | NaT | NaN |
| 2 | 17.31 | 2020-09-01 | 2020-09-23 | 2020-11-05 | 48 |
| 3 | 8.88 | 2020-02-04 | 2020-02-05 | 2020-02-18 | 11 |
| 4 | 7.73 | 2020-11-05 | 2020-11-20 | 2020-12-04 | 22 |
| Stress Events | mean | min | max |
|---|---|---|---|
| New Normal | 0.26% | -16.81% | 18.81% |
with pyfolio.plotting.plotting_context(font_scale=1.1):
pyfolio.create_full_tear_sheet(returns = fixed_r['return'],
benchmark_rets=baseline['return'], set_context=False)
| Start date | 2020-01-02 | |
|---|---|---|
| End date | 2021-12-31 | |
| Total months | 24 | |
| Backtest | ||
| Annual return | 61.514% | |
| Cumulative returns | 161.364% | |
| Annual volatility | 28.8% | |
| Sharpe ratio | 1.81 | |
| Calmar ratio | 1.64 | |
| Stability | 0.96 | |
| Max drawdown | -37.439% | |
| Omega ratio | 1.41 | |
| Sortino ratio | 2.69 | |
| Skew | -0.10 | |
| Kurtosis | 9.80 | |
| Tail ratio | 1.00 | |
| Daily value at risk | -3.422% | |
| Alpha | 0.24 | |
| Beta | 0.87 | |
| Worst drawdown periods | Net drawdown in % | Peak date | Valley date | Recovery date | Duration |
|---|---|---|---|---|---|
| 0 | 37.44 | 2020-02-19 | 2020-03-20 | 2020-05-27 | 71 |
| 1 | 9.52 | 2020-09-02 | 2020-09-21 | 2020-10-22 | 37 |
| 2 | 7.10 | 2021-02-16 | 2021-03-08 | 2021-03-16 | 21 |
| 3 | 6.00 | 2021-12-10 | 2021-12-20 | 2021-12-29 | 14 |
| 4 | 5.67 | 2021-05-07 | 2021-05-12 | 2021-05-28 | 16 |
| Stress Events | mean | min | max |
|---|---|---|---|
| New Normal | 0.21% | -12.10% | 12.20% |